// Module
// ======
// 
// Creates a namespace for a collection of functionality.
// 
// new Module(string path, function body)
// --------------------------------------
// 
// The path should be a string of period separated identifiers e.g. 'Phosphor.Utilities.Types'
// Each component will be created if not present, but not replaced.
// 
// The body of a module is provided as a function, this body should be used to avoid defining or
// assigning to global variables.
// 
// To provide a method or object from a module, assign that value to a property of 'this'.
// 
// e.g. a Module 'Phosphor.Utilities' that provides the method 'hello'.
// 
//    new Module('Phosphor.Utilities', function () {
//        this.hello = function (obj) {
//            window.alert('Hello, Modules');
//        };
//    });
// 
// This method can now be called as `Phosphor.Utilities.hello()`
// 
// If `path` is null, the module body will still be executed but the result is not necessarily 
// stored anywhere.
// 
// If `path` names a module that already exists, the provided body will be run against that module
// and further properties may be defined.
// 
// If `body` returns a value, `path` will be defined as whatever that value is, whether it is an 
// instance of module or not.
// 
// This pattern may be used to provide a Singleton with private and public properties, or to 
// provide a single method/value as a module.
// 
//     new Module('Phosphor.Utilities', function () {
//         // Private variable
//         var name = 'Modules';
// 
//         return {
//             // Public method
//             hello: function (obj) {
//                 window.alert('Hello, ' + name);
//             }
//         };
//     });
// 
// As before, this method can be accessed as `Phosphor.Utilities.hello()`, note that there is no 
// such property as `Phosphor.Utilities.name`. In this case `Phosphor.Utilities` is not an instance
// of `Module`, but  that is probably of no practical consequence. 
// 
// new Module(string path)
// -----------------------
// 
// Create or find the module at 'path' and return it.
// 
// 
// new Module(function body)
// -----------------------
// 
// Calling Module with only a body is the same as calling it with a `null` path.
//  
// 
// new Module()
// ------------
// 
// Create an empty instance of Module, properties can still be added to the module object.
var Module = (function () {
    /*jshint eqnull: true */
    var isFunction, isString,
        _toString = {}.toString; 

    isFunction = function (val) {
        return _toString.call(val) === '[object Function]';
    };

    isString = function (val) {
        return _toString.call(val) === '[object String]';
    };

    function Module(path, body) {
        var module;

        // new Module() or
        // new Module(null)
        if (!(path || body)) {
            // make an honest effort to return an instance of Module   
            return (this instanceof Module ? this : new Module());
        }

        // new Module(function body)
        if (isFunction(path)) {
            return new Module(null, body);
        }

        // new Module(string path) or
        // new Module(string path, function body)
        module = Module.get(path);

        if (isFunction(body)) {
            // replace the module with any
            // returned value
            module = body.call(module) || module;
        }

        Module.set(path, module);

        return module;
    }

    Module.get = function (path) {
        if (path == null) return new Module(); 

        if (!isString(path)) throw new TypeError();

        var components = path.split('.'),
            root = window;

        for (i = 0, l = components.length; i < l; i++) {
            p = components[i];
            if (!(p in root)) root[p] = new Module();
            root = root[p];
        }

        return root;
    };

    Module.set = function (path, object) {
        if (path == null) return object;

        if (!isString(path)) throw new TypeError();
        
        var components = path.split('.'),
            key = components[components.length - 1],
            root = window;

        for (i = 0, l = components.length - 1; i < l; i++) {
            p = components[i];
            if (!(p in root)) root[p] = new Module();
            root = root[p];
        }

        root[key] = object;

        return object;
    };

    return Module;
}());
